/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package queues;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Properties;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.input.KeyEvent;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import server.Counter;
import server.IInfoPanelServiceRegisterUnauthorizedAccessExceptionFaultFaultMessage;
import server.IKioskServiceRegisterUnauthorizedAccessExceptionFaultFaultMessage;
import server.QueueInfo;
import server.Subject;

/**
 *
 * @author Renato Rosa <2101076@my.ipleiria.pt>
 * @param P the type of the proxy port to contact the server
 */
public abstract class QueuesClient<P> extends Application implements NotificationsObserver {

    protected P proxy;
    protected Stage stage;
    protected NotificationsService notificationsService;
    protected List<Subject> subjects;
    /*
     * Properties
     */
    private String propertiesFile;
    protected Properties properties;
    protected String clientId;
    protected int maxTries;
    protected boolean useLocalhost;
    protected long waitingMillis;
    protected String serverAddress;
    protected URL serverWsdlLocation;
    protected boolean initialized = false;
    protected boolean registered = false;

    /**
     * Get the value of registered
     *
     * @return the value of registered
     */
    public boolean isRegistered() {
        return registered;
    }

    /**
     *
     * @param propertiesFile the name of the file containing the client
     * initializing properties
     */
    public QueuesClient(String propertiesFile) {
        this.propertiesFile = propertiesFile;
    }

    protected abstract void registerImpl() throws Exception;

    protected abstract void unregisterImpl() throws Exception;

    /**
     * Get the value of initialized
     *
     * @return the value of initialized
     */
    public boolean isInitialized() {
        return initialized;
    }

    /**
     *
     * @return the Stage, where scenes are shown
     */
    public Stage getStage() {
        return stage;
    }

    @Override
    public void initialize(List<Subject> subjects, List<Counter> contadores, List<QueueInfo> senhas) throws NotInitializedException {
        if (subjects.isEmpty()) {
            throw new NotInitializedException("Subjects list is empty");
        }
        this.subjects = subjects;
    }

    public void register() throws Exception {
        int tries = 0;
        notifyPreloader(new QueuesPreloader.RegistrationNotification(false, "Conectando ao servidor..."));
        while (tries < maxTries) {
            try {
                //each client must call its own object
                registerImpl();
                notifyPreloader(new QueuesPreloader.RegistrationNotification(true, "OK"));
                registered = true;
                return;
            } catch (IKioskServiceRegisterUnauthorizedAccessExceptionFaultFaultMessage |
                    IInfoPanelServiceRegisterUnauthorizedAccessExceptionFaultFaultMessage e) {
                //invalid id
                System.err.println("Error registering client: " + e.getMessage());
                notifyPreloader(new QueuesPreloader.RegistrationNotification(false, "O id não é válido. Consulte o administrador."));
                return;
            } catch (Exception e1) {
                System.err.println("Error registering client: " + e1.getMessage());
                notifyPreloader(new QueuesPreloader.RegistrationNotification(false, "Tentando conectar ao servidor... Tentativa "
                        + String.format("%d de %d", tries + 1, maxTries)));
                tries++;

                //wait a bit, maybe the server needs some time...
                Thread.sleep(waitingMillis);
            }
        }

        //enough tries, let's stop and inform the user
        System.err.println("Não foi possível contactar o servidor");
        notifyPreloader(new QueuesPreloader.RegistrationNotification(false, "Não foi possível contactar o servidor"));
    }

    //Inspired in FXML-LoginDemo sample
    /**
     *
     * @param fxml the name of the fxml file to render and present in stage
     * @return the controller of the scene
     * @throws Exception
     */
    protected Initializable setSceneContent(String fxml) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/" + fxml));
        final Parent root = (Parent) loader.load();
        Platform.runLater(new Runnable() {
            @Override
            public void run() {
                stage.setScene(new Scene(root));
                stage.sizeToScene();
            }
        });
        return (Initializable) loader.getController();
    }

    @Override
    public void init() throws Exception {
        super.init();

        try (InputStream stream = new FileInputStream(propertiesFile)) {
            if (stream != null) {
                properties = new Properties();
                properties.load(stream);
                maxTries = Integer.parseInt(properties.getProperty("maxTries", "5"));
                waitingMillis = Long.parseLong(properties.getProperty("waitingMillis", "5000"));
                serverAddress = properties.getProperty("serverAddress", "http://localhost:1111/QueuesService.svc");
                clientId = properties.getProperty("clientId", "");
                serverWsdlLocation = new URL(serverAddress + "?wsdl");
                useLocalhost = Boolean.parseBoolean(properties.getProperty("useLocalhost", "true"));
            }
        } catch (IOException ex) {
            notifyPreloader(new QueuesPreloader.RegistrationNotification(false, "Não foi possível ler o ficheiro de configurações."));
            System.err.println("Error: " + ex.getMessage());
            return;
        }

        try {
            //init() isn't executed in FX Thread, so it's a better place to start the service, instead of start().
            //the client receives notifications. Observer pattern allows a generic design and easier changes
            notificationsService.addObserver(this);
            notificationsService.start(useLocalhost);
            register();
        } catch (Exception e) {
            notifyPreloader(new QueuesPreloader.RegistrationNotification(false, "Ocorreu um erro: " + e.getMessage()));
        }
    }

    @Override
    public void start(Stage mainStage) throws Exception {
        stage = mainStage;
        //allow to close the application pressing 'Q'
        stage.addEventHandler(KeyEvent.ANY, new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent t) {
                if (t.getCharacter().equalsIgnoreCase("Q")) {
                    Platform.exit();
                } else if (t.getCharacter().equalsIgnoreCase("M")) {
                    stage.setIconified(!stage.isIconified());
                }
            }
        });

        stage.initStyle(StageStyle.UNDECORATED);
        //stage.setFullScreen(true);
        stage.setX(0);
        stage.setY(0);
        //show the scene only if the client is registered, otherwise it's useless
        if (registered) {
            stage.show();
        }
    }

    @Override
    public void stop() throws Exception {
        //let's do this in background, because if there is a problem, we don't want to wait to close the application
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    unregisterImpl();
                } catch (Exception ex) {
                    System.err.println("Error: " + ex.getMessage());
                }
            }
        }).start();

        //no more notifications
        notificationsService.stop();
        super.stop();
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}
